package edu.hda.pt.client.connections;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.BufferOverflowException;

import org.xsocket.connection.BlockingConnection;
import org.xsocket.connection.IBlockingConnection;
import org.xsocket.connection.INonBlockingConnection;
import org.xsocket.connection.NonBlockingConnection;

import edu.hda.pt.client.Config;
import edu.hda.pt.client.PTClient;
import edu.hda.pt.client.connections.xsocket.ClientHandler;
import edu.hda.pt.utils.Stream;
import edu.hda.pt.utils.TestMethod;
import edu.hda.pt.utils.TestObject;
import edu.hda.pt.utils.Stream.StreamState;
import edu.hda.pt.utils.connections.xsocket.XSocketConstants;

/**
 * Client implementation for the connection method xSocket
 * 
 * @author Marc Hammerton
 * 
 */
public class PTClientXSocket extends PTClient {

	private String serverIP;

	private IBlockingConnection bc = null;
	private INonBlockingConnection nbc = null;
	private ClientHandler handler = null;

	public Stream stream;

	/**
	 * Constructor for setting up the xSocket client.
	 * 
	 * @param serverIP
	 *            - The IP address of the xSocket server
	 */
	public PTClientXSocket(String serverIP) {
		this.serverIP = serverIP;
	}

	/**
	 * @see edu.hda.pt.client.PTClient#connect()
	 */
	@Override
	public void connect() {
		try {

			if (PTClient.test2run == TestMethod.STREAMING) {
				handler = new ClientHandler(this);
				nbc = new NonBlockingConnection(serverIP,
						XSocketConstants.PORT, handler);
				nbc.setAutoflush(false);
			} else {
				bc = new BlockingConnection(serverIP, XSocketConstants.PORT);
				bc.setAutoflush(false);
			}
		} catch (IOException e) {
			System.out.println(this.clientID + " Exception:");
			e.printStackTrace();
		}
	}

	/**
	 * @see edu.hda.pt.client.PTClient#disconnect()
	 */
	@Override
	public void disconnect() {
		try {
			if (PTClient.test2run == TestMethod.STREAMING) {
				nbc.close();
			} else {
				bc.close();
			}

		} catch (IOException e) {
			System.out.println(this.clientID + " Exception:");
			e.printStackTrace();
		}

	}

	/**
	 * @see edu.hda.pt.client.PTClient#sendBool(int, boolean)
	 */
	@Override
	protected void sendBool(int messageId, boolean boolPrim) {
		// Send message
		try {
			bc.write((short) Config.HANDLEBOOL);
			bc.write(boolPrim == true ? (byte) 1 : (byte) 0);
			bc.flush();
		} catch (BufferOverflowException e) {
			System.out.println(this.clientID + " Exception:");
			e.printStackTrace();
		} catch (IOException e) {
			System.out.println(this.clientID + " Exception:");
			e.printStackTrace();
		}

		// Get response
		try {
			@SuppressWarnings("unused")
			boolean response = bc.readByte() == 1 ? true : false;
		} catch (UnsupportedEncodingException e) {
			System.out.println(this.clientID + " Exception:");
			e.printStackTrace();
		} catch (IOException e) {
			System.out.println(this.clientID + " Exception:");
			e.printStackTrace();
		}

	}

	/**
	 * @see edu.hda.pt.client.PTClient#sendBytes(int, byte[])
	 */
	@Override
	protected void sendBytes(int messageId, byte[] bytes) {
		// Send message
		try {
			bc.write((short) Config.HANDLEBYTES);
			bc.write(bytes.length);
			bc.write(bytes);
			bc.flush();
		} catch (BufferOverflowException e) {
			System.out.println(this.clientID + " Exception:");
			e.printStackTrace();
		} catch (IOException e) {
			System.out.println(this.clientID + " Exception:");
			e.printStackTrace();
		}

		// Get response
		try {
			int length = bc.readInt();
			@SuppressWarnings("unused")
			byte[] response = bc.readBytesByLength(length);
		} catch (UnsupportedEncodingException e) {
			System.out.println(this.clientID + " Exception:");
			e.printStackTrace();
		} catch (IOException e) {
			System.out.println(this.clientID + " Exception:");
			e.printStackTrace();
		}

	}

	/**
	 * @see edu.hda.pt.client.PTClient#sendInt(int, int)
	 */
	@Override
	protected void sendInt(int messageId, int intPrim) {
		// Send message
		try {
			bc.write((short) Config.HANDLEINTEGER);
			bc.write(intPrim);
			bc.flush();
		} catch (BufferOverflowException e) {
			System.out.println(this.clientID + " Exception:");
			e.printStackTrace();
		} catch (IOException e) {
			System.out.println(this.clientID + " Exception:");
			e.printStackTrace();
		}

		// Get response
		try {
			@SuppressWarnings("unused")
			int response = bc.readInt();
		} catch (UnsupportedEncodingException e) {
			System.out.println(this.clientID + " Exception:");
			e.printStackTrace();
		} catch (IOException e) {
			System.out.println(this.clientID + " Exception:");
			e.printStackTrace();
		}

	}

	/**
	 * @see edu.hda.pt.client.PTClient#sendLong(int, long)
	 */
	@Override
	protected void sendLong(int messageId, long longPrim) {
		// Send message
		try {
			bc.write((short) Config.HANDLELONG);
			bc.write(longPrim);
			bc.flush();
		} catch (BufferOverflowException e) {
			System.out.println(this.clientID + " Exception:");
			e.printStackTrace();
		} catch (IOException e) {
			System.out.println(this.clientID + " Exception:");
			e.printStackTrace();
		}

		// Get response
		try {
			@SuppressWarnings("unused")
			long response = bc.readLong();
		} catch (UnsupportedEncodingException e) {
			System.out.println(this.clientID + " Exception:");
			e.printStackTrace();
		} catch (IOException e) {
			System.out.println(this.clientID + " Exception:");
			e.printStackTrace();
		}

	}

	/**
	 * @see edu.hda.pt.client.PTClient#sendObject(int,
	 * edu.hda.pt.utils.TestObject)
	 */
	@Override
	protected void sendObject(int messageId, TestObject testObject) {
		// Send message
		try {
			// Convert Object to ByteArray
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			ObjectOutputStream oos = new ObjectOutputStream(bos);
			oos.writeObject(testObject);
			oos.flush();
			oos.close();
			bos.close();

			byte[] data = bos.toByteArray();
			bc.write((short) Config.HANDLEOBJECT);
			bc.write(data.length);
			bc.write(data);
			bc.flush();
		} catch (BufferOverflowException e) {
			System.out.println(this.clientID + " Exception:");
			e.printStackTrace();
		} catch (IOException e) {
			System.out.println(this.clientID + " Exception:");
			e.printStackTrace();
		}

		// Get response
		try {
			int length = bc.readInt();
			byte[] bytes = bc.readBytesByLength(length);

			ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
			ObjectInputStream ois = new ObjectInputStream(bis);
			@SuppressWarnings("unused")
			TestObject value = (TestObject) ois.readObject();
			ois.close();
			bis.close();
		} catch (UnsupportedEncodingException e) {
			System.out.println(this.clientID + " Exception:");
			e.printStackTrace();
		} catch (IOException e) {
			System.out.println(this.clientID + " Exception:");
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			System.out.println(this.clientID + " Exception:");
			e.printStackTrace();
		}

	}

	/**
	 * @see edu.hda.pt.client.PTClient#sendString(int, java.lang.String)
	 */
	@Override
	protected void sendString(int messageId, String text) {
		// Send message
		try {
			byte[] data = text.getBytes();
			bc.write((short) Config.HANDLESTRING);
			bc.write(data.length);
			bc.write(data);
			bc.flush();
		} catch (BufferOverflowException e) {
			System.out.println(this.clientID + " Exception:");
			e.printStackTrace();
		} catch (IOException e) {
			System.out.println(this.clientID + " Exception:");
			e.printStackTrace();
		}

		// Get response
		try {
			int length = bc.readInt();
			@SuppressWarnings("unused")
			String response = new String(bc.readBytesByLength(length));
		} catch (UnsupportedEncodingException e) {
			System.out.println(this.clientID + " Exception:");
			e.printStackTrace();
		} catch (IOException e) {
			System.out.println(this.clientID + " Exception:");
			e.printStackTrace();
		}
	}

	/**
	 * @see edu.hda.pt.client.PTClient#sendStreamingRequest(int, int)
	 */
	@Override
	protected void sendStreamingRequest(int fs) {

		System.out.println(this.clientID + ": requesting stream");
		stream = new Stream(fs);
		stream.setState(StreamState.START);

		try {
			nbc.write((short) Config.HANDLESTREAM_START);
			nbc.write(fs);
			nbc.flush();
		} catch (BufferOverflowException e) {
			System.out.println(this.clientID + " Exception:");
			e.printStackTrace();
		} catch (IOException e) {
			System.out.println(this.clientID + " Exception:");
			e.printStackTrace();
		}
	}

	/**
	 * @see edu.hda.pt.client.PTClient#terminateStreaming()
	 */
	@Override
	protected void terminateStreaming() {
		if (stream != null && stream.getState() == StreamState.RUNNING) {
			
			stream.setState(StreamState.FINISH);
			try {
				nbc.write((short) Config.HANDLESTREAM_STOP);
				nbc.flush();
			} catch (BufferOverflowException e) {
				System.out.println(this.clientID + " Exception:");
				e.printStackTrace();
			} catch (IOException e) {
				System.out.println(this.clientID + " Exception:");
				e.printStackTrace();
			}
			try {
				Thread.sleep(15000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(this.clientID + ": stopping stream");
		}
	}
}
